package com.jneagle.xlstool.bzwgjmx.impl.handler;

import com.dwarfeng.dutil.basic.io.FileUtil;
import com.dwarfeng.subgrade.stack.exception.HandlerException;
import com.jneagle.xlstool.bzwgjmx.stack.bean.entity.ExportData;
import com.jneagle.xlstool.bzwgjmx.stack.bean.entity.ExportErrorInfo;
import com.jneagle.xlstool.bzwgjmx.stack.exception.WrongPasswordException;
import com.jneagle.xlstool.bzwgjmx.stack.handler.DataExportHandler;
import com.jneagle.xlstool.bzwgjmx.stack.service.ExportDataMaintainService;
import com.jneagle.xlstool.bzwgjmx.stack.service.ExportErrorInfoMaintainService;
import com.jneagle.xlstool.bzwgjmx.stack.structure.ProgressStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

@Component
public class DataExportHandlerImpl extends AbstractProgressHandler implements DataExportHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(DataExportHandlerImpl.class);
    private static final short TWIPS_PER_PIXEL = 20;

    private final ExportDataMaintainService exportDataMaintainService;
    private final ExportErrorInfoMaintainService exportErrorInfoMaintainService;

    @Value("${export_data.sheet.index}")
    private int sheetIndex;
    @Value("${export_data.row.first_data_index}")
    private int rowFirstDataIndex;
    @Value("${export_data.column.project_no_index}")
    private int columnProjectNoIndex;
    @Value("${export_data.column.part_no_index}")
    private int columnPartNoIndex;
    @Value("${export_data.column.classification_name_index}")
    private int columnClassificationNameIndex;
    @Value("${export_data.column.production_name_index}")
    private int columnProductionNameIndex;
    @Value("${export_data.column.specification_index}")
    private int columnSpecificationIndex;
    @Value("${export_data.column.demand_index}")
    private int columnDemandIndex;
    @Value("${export_data.column.remark_index}")
    private int columnRemarkIndex;

    @Value("${export_data.policy.overwrite_structured_data}")
    private boolean overwriteStructuredData;

    @Value("${export_data.decoration.row_height}")
    private short decorationRowHeight;

    public DataExportHandlerImpl(
            ExportDataMaintainService exportDataMaintainService,
            ExportErrorInfoMaintainService exportErrorInfoMaintainService
    ) {
        this.exportDataMaintainService = exportDataMaintainService;
        this.exportErrorInfoMaintainService = exportErrorInfoMaintainService;
    }

    @Override
    public void exportData(File inputFile, File outputFile, String inputPassword, String outputPassword)
            throws HandlerException {
        try {
            // 广播进度变更事件。
            fireProgressChanged(ProgressStatus.UNCERTAIN);

            // 清空相关实体的维护服务。
            exportErrorInfoMaintainService.clear();

            // 如果输入文件和输出文件相等，将原始文件复制为 .bak; 否则，直接将输入文件复制到输出文件。
            if (Objects.equals(inputFile, outputFile)) {
                File backFile = new File(inputFile.getParentFile(), inputFile.getName() + ".bak");
                FileUtil.fileCopy(inputFile, backFile);
            } else {
                FileUtil.fileCopy(inputFile, outputFile);
            }

            // 获取需要导出的所有数据，并定义错误列表。
            List<ExportData> exportDataList = exportDataMaintainService.lookupAsList();
            List<ExportErrorInfo> exportErrorInfoList = new ArrayList<>();

            fireProgressChanged(0, exportDataList.size());

            // 加载密码。
            org.apache.poi.hssf.record.crypto.Biff8EncryptionKey.setCurrentUserPassword(
                    StringUtils.isEmpty(inputPassword) ? null : inputPassword
            );
            // 调用 poi 对 excel 进行操作。
            try (
                    InputStream in = Files.newInputStream(outputFile.toPath());
                    HSSFWorkbook workbook = new HSSFWorkbook(in);
                    OutputStream out = Files.newOutputStream(outputFile.toPath())
            ) {
                // 获取公式更新器。
                FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator(workbook);
                // 获取工作簿。
                Sheet sheet = workbook.getSheetAt(sheetIndex);
                // 遍历导出数据，写入表数据。
                for (int i = 0; i < exportDataList.size(); i++) {
                    // 获取导出数据。
                    ExportData exportData = exportDataList.get(i);

                    // 获得当前行，如果不存在就创建。
                    int rowIndex = i + rowFirstDataIndex;
                    Row row = sheet.getRow(rowIndex);
                    if (Objects.isNull(row)) {
                        row = sheet.createRow(rowIndex);
                    }

                    // 行操作。
                    decorateRow(row);
                    if (!exportData.isStructuredDataFlag() || overwriteStructuredData) {
                        exportSingleRow(rowIndex, row, exportData, exportErrorInfoList);
                    }
                    updateRow(formulaEvaluator, row);

                    // 广播进度。
                    fireProgressChanged(i + 1, exportDataList.size());
                }
                // 加载密码。
                org.apache.poi.hssf.record.crypto.Biff8EncryptionKey.setCurrentUserPassword(
                        StringUtils.isEmpty(outputPassword) ? null : outputPassword
                );
                // 写入文件。
                workbook.write(out);
            }

            // 将错误插入至导出错误维护服务。
            exportErrorInfoMaintainService.batchInsert(exportErrorInfoList);
        } catch (org.apache.poi.EncryptedDocumentException e) {
            throw new WrongPasswordException(e);
        } catch (Exception e) {
            throw new HandlerException(e);
        } finally {
            // 广播进度变更事件。
            fireProgressChanged(ProgressStatus.IDLE);
        }
    }

    private void decorateRow(Row row) {
        // 设置行高。
        row.setHeight((short) (TWIPS_PER_PIXEL * decorationRowHeight));
    }

    private void exportSingleRow(int rowIndex, Row row, ExportData exportData, List<ExportErrorInfo> exportErrorInfoList) {
        try {
            // 定义通用变量。
            Cell currentCell;

            // 项目号。
            currentCell = row.createCell(columnProjectNoIndex);
            currentCell.setCellValue(exportData.getProjectNo());

            // 部件号。
            currentCell = row.createCell(columnPartNoIndex);
            currentCell.setCellValue(exportData.getPartNo());

            // 类别名称。
            currentCell = row.createCell(columnClassificationNameIndex);
            currentCell.setCellValue(exportData.getClassificationName());

            // 产品名称。
            currentCell = row.createCell(columnProductionNameIndex);
            currentCell.setCellValue(exportData.getProductionName());

            // 规格。
            currentCell = row.createCell(columnSpecificationIndex);
            currentCell.setCellValue(exportData.getSpecification());

            // 单需。
            // 获取单需字段的值，如果值可以转换为数字，则设置为数字类型，否则设置为字符串类型。
            String demand = exportData.getDemand();
            if (demand.matches("^\\d+(\\.\\d+)?$")) {
                currentCell = row.createCell(columnDemandIndex);
                currentCell.setCellValue(Double.parseDouble(demand));
            } else {
                currentCell = row.createCell(columnDemandIndex);
                currentCell.setCellValue(demand);
            }

            // 备注。
            currentCell = row.createCell(columnRemarkIndex);
            currentCell.setCellValue(exportData.getRemark());
        } catch (Exception e) {
            LOGGER.warn(
                    "导出数据表的第 " + rowIndex + " 行(对应数据表是第 "
                            + (rowIndex + 1) + " 行)数据时出现异常，异常信息为: ", e
            );
            ExportErrorInfo errorInfo = new ExportErrorInfo(null, rowIndex, "ui.label.017");
            exportErrorInfoList.add(errorInfo);
        }
    }

    private void updateRow(FormulaEvaluator formulaEvaluator, Row row) {
        for (Iterator<Cell> cells = row.cellIterator(); cells.hasNext(); ) {
            Cell cell = cells.next();
            if (Objects.equals(cell.getCellType(), CellType.FORMULA)) {
                formulaEvaluator.evaluateFormulaCell(cell);
            }
        }
    }
}
